/* global jQuery */

(function ($) {
  'use strict';

  var options = {
    propagateSupportedGesture: false
  };

  function init(plot) {
    plot.hooks.processOptions.push(initTouchNavigation);
  }

  function initTouchNavigation(plot, options) {
    var gestureState = {
        twoTouches: false,
        currentTapStart: {x: 0, y: 0},
        currentTapEnd: {x: 0, y: 0},
        prevTap: {x: 0, y: 0},
        currentTap: {x: 0, y: 0},
        interceptedLongTap: false,
        isUnsupportedGesture: false,
        prevTapTime: null,
        tapStartTime: null,
        longTapTriggerId: null
      },
      maxDistanceBetweenTaps = 20,
      maxIntervalBetweenTaps = 500,
      maxLongTapDistance = 20,
      minLongTapDuration = 1500,
      pressedTapDuration = 125,
      mainEventHolder;

    function interpretGestures(e) {
      var o = plot.getOptions();

      if (!o.pan.active && !o.zoom.active) {
        return;
      }

      updateOnMultipleTouches(e);
      mainEventHolder.dispatchEvent(new CustomEvent('touchevent', {detail: e}));

      if (isPinchEvent(e)) {
        executeAction(e, 'pinch');
      } else {
        executeAction(e, 'pan');
        if (!wasPinchEvent(e)) {
          if (isDoubleTap(e)) {
            executeAction(e, 'doubleTap');
          }
          executeAction(e, 'tap');
          executeAction(e, 'longTap');
        }
      }
    }

    function executeAction(e, gesture) {
      switch (gesture) {
        case 'pan':
          pan[e.type](e);
          break;
        case 'pinch':
          pinch[e.type](e);
          break;
        case 'doubleTap':
          doubleTap.onDoubleTap(e);
          break;
        case 'longTap':
          longTap[e.type](e);
          break;
        case 'tap':
          tap[e.type](e);
          break;
      }
    }

    function bindEvents(plot, eventHolder) {
      mainEventHolder = eventHolder[0];
      eventHolder[0].addEventListener('touchstart', interpretGestures, false);
      eventHolder[0].addEventListener('touchmove', interpretGestures, false);
      eventHolder[0].addEventListener('touchend', interpretGestures, false);
    }

    function shutdown(plot, eventHolder) {
      eventHolder[0].removeEventListener('touchstart', interpretGestures);
      eventHolder[0].removeEventListener('touchmove', interpretGestures);
      eventHolder[0].removeEventListener('touchend', interpretGestures);
      if (gestureState.longTapTriggerId) {
        clearTimeout(gestureState.longTapTriggerId);
        gestureState.longTapTriggerId = null;
      }
    }

    var pan = {
      touchstart: function (e) {
        updatePrevForDoubleTap();
        updateCurrentForDoubleTap(e);
        updateStateForLongTapStart(e);

        mainEventHolder.dispatchEvent(new CustomEvent('panstart', {detail: e}));
      },

      touchmove: function (e) {
        preventEventBehaviors(e);

        updateCurrentForDoubleTap(e);
        updateStateForLongTapEnd(e);

        if (!gestureState.isUnsupportedGesture) {
          mainEventHolder.dispatchEvent(new CustomEvent('pandrag', {detail: e}));
        }
      },

      touchend: function (e) {
        preventEventBehaviors(e);

        if (wasPinchEvent(e)) {
          mainEventHolder.dispatchEvent(new CustomEvent('pinchend', {detail: e}));
          mainEventHolder.dispatchEvent(new CustomEvent('panstart', {detail: e}));
        } else if (noTouchActive(e)) {
          mainEventHolder.dispatchEvent(new CustomEvent('panend', {detail: e}));
        }
      }
    };

    var pinch = {
      touchstart: function (e) {
        mainEventHolder.dispatchEvent(new CustomEvent('pinchstart', {detail: e}));
      },

      touchmove: function (e) {
        preventEventBehaviors(e);
        gestureState.twoTouches = isPinchEvent(e);
        if (!gestureState.isUnsupportedGesture) {
          mainEventHolder.dispatchEvent(new CustomEvent('pinchdrag', {detail: e}));
        }
      },

      touchend: function (e) {
        preventEventBehaviors(e);
      }
    };

    var doubleTap = {
      onDoubleTap: function (e) {
        preventEventBehaviors(e);
        mainEventHolder.dispatchEvent(new CustomEvent('doubletap', {detail: e}));
      }
    };

    var longTap = {
      touchstart: function (e) {
        longTap.waitForLongTap(e);
      },

      touchmove: function (e) {
      },

      touchend: function (e) {
        if (gestureState.longTapTriggerId) {
          clearTimeout(gestureState.longTapTriggerId);
          gestureState.longTapTriggerId = null;
        }
      },

      isLongTap: function (e) {
        var currentTime = new Date().getTime(),
          tapDuration = currentTime - gestureState.tapStartTime;
        if (tapDuration >= minLongTapDuration && !gestureState.interceptedLongTap) {
          if (distance(gestureState.currentTapStart.x, gestureState.currentTapStart.y, gestureState.currentTapEnd.x, gestureState.currentTapEnd.y) < maxLongTapDistance) {
            gestureState.interceptedLongTap = true;
            return true;
          }
        }
        return false;
      },

      waitForLongTap: function (e) {
        var longTapTrigger = function () {
          if (longTap.isLongTap(e)) {
            mainEventHolder.dispatchEvent(new CustomEvent('longtap', {detail: e}));
          }
          gestureState.longTapTriggerId = null;
        };
        if (!gestureState.longTapTriggerId) {
          gestureState.longTapTriggerId = setTimeout(longTapTrigger, minLongTapDuration);
        }
      }
    };

    var tap = {
      touchstart: function (e) {
        gestureState.tapStartTime = new Date().getTime();
      },

      touchmove: function (e) {
      },

      touchend: function (e) {
        if (tap.isTap(e)) {
          mainEventHolder.dispatchEvent(new CustomEvent('tap', {detail: e}));
          preventEventBehaviors(e);
        }
      },

      isTap: function (e) {
        var currentTime = new Date().getTime(),
          tapDuration = currentTime - gestureState.tapStartTime;
        if (tapDuration <= pressedTapDuration) {
          if (distance(gestureState.currentTapStart.x, gestureState.currentTapStart.y, gestureState.currentTapEnd.x, gestureState.currentTapEnd.y) < maxLongTapDistance) {
            return true;
          }
        }
        return false;
      }
    };

    if (options.pan.enableTouch === true || options.zoom.enableTouch) {
      plot.hooks.bindEvents.push(bindEvents);
      plot.hooks.shutdown.push(shutdown);
    }
    ;

    function updatePrevForDoubleTap() {
      gestureState.prevTap = {
        x: gestureState.currentTap.x,
        y: gestureState.currentTap.y
      };
    };

    function updateCurrentForDoubleTap(e) {
      gestureState.currentTap = {
        x: e.touches[0].pageX,
        y: e.touches[0].pageY
      };
    }

    function updateStateForLongTapStart(e) {
      gestureState.tapStartTime = new Date().getTime();
      gestureState.interceptedLongTap = false;
      gestureState.currentTapStart = {
        x: e.touches[0].pageX,
        y: e.touches[0].pageY
      };
      gestureState.currentTapEnd = {
        x: e.touches[0].pageX,
        y: e.touches[0].pageY
      };
    };

    function updateStateForLongTapEnd(e) {
      gestureState.currentTapEnd = {
        x: e.touches[0].pageX,
        y: e.touches[0].pageY
      };
    };

    function isDoubleTap(e) {
      var currentTime = new Date().getTime(),
        intervalBetweenTaps = currentTime - gestureState.prevTapTime;

      if (intervalBetweenTaps >= 0 && intervalBetweenTaps < maxIntervalBetweenTaps) {
        if (distance(gestureState.prevTap.x, gestureState.prevTap.y, gestureState.currentTap.x, gestureState.currentTap.y) < maxDistanceBetweenTaps) {
          e.firstTouch = gestureState.prevTap;
          e.secondTouch = gestureState.currentTap;
          return true;
        }
      }
      gestureState.prevTapTime = currentTime;
      return false;
    }

    function preventEventBehaviors(e) {
      if (!gestureState.isUnsupportedGesture) {
        e.preventDefault();
        if (!plot.getOptions().propagateSupportedGesture) {
          e.stopPropagation();
        }
      }
    }

    function distance(x1, y1, x2, y2) {
      return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
    }

    function noTouchActive(e) {
      return (e.touches && e.touches.length === 0);
    }

    function wasPinchEvent(e) {
      return (gestureState.twoTouches && e.touches.length === 1);
    }

    function updateOnMultipleTouches(e) {
      if (e.touches.length >= 3) {
        gestureState.isUnsupportedGesture = true;
      } else {
        gestureState.isUnsupportedGesture = false;
      }
    }

    function isPinchEvent(e) {
      if (e.touches && e.touches.length >= 2) {
        if (e.touches[0].target === plot.getEventHolder() &&
          e.touches[1].target === plot.getEventHolder()) {
          return true;
        }
      }
      return false;
    }
  }

  $.plot.plugins.push({
    init: init,
    options: options,
    name: 'navigateTouch',
    version: '0.3'
  });
})(jQuery);
